#!/usr/bin/env python
# coding: utf-8

# # 背景介绍
# <font size=4>说到垃圾邮件，想必读者们都是深受其害吧~不管是企业内部工作邮箱，还是个人邮箱，一封封垃圾邮件那是屡禁不绝……</font>
#
# <font size=4>来自商家的广告、打折促销信息、澳门博彩邮件、理财推广信息等，不管如何归类，但是总有漏网之鱼……更重要的是，不同用户对于垃圾邮件的定义并不一致。</font>
#
# ![file](https://ai-studio-static-online.cdn.bcebos.com/bab333231fad4417b4a311665297ee18787f8e574f004b588be097cf9278fe95)
#
#
# <font size=4>如果是在企业里，那更是一把辛酸泪。大部分用户网络安全意识比较一般，万一误点垃圾邮件上钩，或者因为垃圾邮件淹没了工作中的关键信件，可能给企业造成损失。</font>
#
# <font size=4>而且，如果企业涉及到非英语国家的跨国业务，那就更是惨上加惨——连邮件内容都看不懂，如何判断它是否是垃圾邮件？</font>
#  <img src="https://ai-studio-static-online.cdn.bcebos.com/290c12f4c64846bbb6ffb285d817a74bbad9d721e85d445d80ee2984e3e8183c" width = "60" height = "60" />
#
#
# <font size=4>垃圾邮件花样实在太多了，传统垃圾邮件拦截器总是有点跟不上，因此，这里尝试使用深度学习的方法，进行垃圾邮件的分类识别——期待假以时日，这种定制化的垃圾邮件识别工具能大幅提升用户的邮箱使用体验吧~</font>![https://ai-studio-static-online.cdn.bcebos.com/d500f483e55544a5b929abad59de208f180c068cc81648009fab60a0b6d9bda2](https://ai-studio-static-online.cdn.bcebos.com/d500f483e55544a5b929abad59de208f180c068cc81648009fab60a0b6d9bda2)

# > 本文是《使用PaddleNLP进行垃圾邮件识别》系列第一篇，该系列持续更新中……
# >
# > 该系列还有个姊妹篇，[《使用PaddleNLP进行恶意网页识别》](https://aistudio.baidu.com/aistudio/projectdetail/1812025)，欢迎感兴趣的读者点进来交流评论![https://ai-studio-static-online.cdn.bcebos.com/d500f483e55544a5b929abad59de208f180c068cc81648009fab60a0b6d9bda2](https://ai-studio-static-online.cdn.bcebos.com/d500f483e55544a5b929abad59de208f180c068cc81648009fab60a0b6d9bda2)

# # 系列目录
#
# - [使用PaddleNLP识别垃圾邮件（一）：准确率98.5%的垃圾邮件分类器](https://aistudio.baidu.com/aistudio/projectdetail/1955576)
#     - 使用PaddleNLP的文本分类LSTM模型，提取中文邮件内容判断邮件是否为垃圾邮件。
# - [使用PaddleNLP识别垃圾邮件（二）：用BERT做中文邮件内容分类](https://aistudio.baidu.com/aistudio/projectdetail/1988755)
#     - 使用PaddleNLP的BERT预训练模型，根据提取的中文邮件内容判断邮件是否为垃圾邮件。
# - [使用PaddleNLP识别垃圾邮件（三）：用ELECTRA做英文邮件标题分类](https://aistudio.baidu.com/aistudio/projectdetail/1988755)
#     - 介绍在Python中解析eml邮件内容的办法：email模块和mmpi库
#     - 使用PaddleNLP的ELECTRA预训练模型，根据提取的英文邮件标题判断邮件是否为垃圾邮件。
# - [使用PaddleNLP识别垃圾邮件（四）：用RoBERTa做中文邮件标题分类](https://aistudio.baidu.com/aistudio/projectdetail/2066132)
#     - 升级到最新自定义数据集方法；
#     - 使用PaddleNLP模型库，大幅简化开发流程；
#     - 使用PaddleNLP的RoBERTa预训练模型，根据提取的中文邮件标题判断邮件是否为垃圾邮件；
#     - 完成完整的批量邮件分类部署流程。

# # 整体思路
#
# - 垃圾邮件内容分类（通过提取垃圾邮件内容进行判断）
# 	- 中文垃圾邮件分类
# 	- 英文垃圾邮件分类
#    - （如有数据集）多语种垃圾邮件分类
# - 垃圾邮件标题分类（针对一些只能抓取到垃圾邮件标题的旧设备，更加考验模型对短文本的分类效果）
# - 与恶意网页识别结合，沙盒式的完整解决方案

# # 数据集介绍
#
# <font size=4>[TREC 2006 Spam Track Public Corpora](https://plg.uwaterloo.ca/~gvcormac/treccorpus06/about.html)是一个公开的垃圾邮件语料库，由国际文本检索会议提供，分为英文数据集（trec06p）和中文数据集（trec06c），其中所含的邮件均来源于真实邮件保留了邮件的原有格式和内容。</font>
#
# <font size=4>除TREC 2006外，还有TREC 2005和TREC 2007的英文垃圾邮件数据集（对，没有中文），本项目中，仅使用TREC 200提供的中文数据集进行演示。TREC 2005-2007的垃圾邮件数据集，均已整理在项目挂载的数据集中，感兴趣的读者可以自行fork。</font>
#
# <font size=4>文件目录形式：delay和full分别是一种垃圾邮件过滤器的过滤机制，full目录下，是理想的邮件分类结果，我们可以视为研究的标签。</font>
# ```
# trec06c
# │
# └───data
# │   │   000
# │   │   001
# │   │   ...
# │   └───215
# └───delay
# │   │   index
# └───full
# │   │   index
# ```
#
# 邮件内容样本示例：
# ```
# 负责人您好我是深圳金海实业有限公司 广州 东莞 等省市有分公司我司有良好的社会关系和实力 因每月进项多出项少现有一部分发票可优惠对外代开税率较低 增值税发票为 其它国税 地税运输 广告等普通发票为 的税点 还可以根据数目大小来衡量优惠的多少 希望贵公司 商家等来电商谈欢迎合作本公司郑重承诺所用票据可到税务局验证或抵扣欢迎来电进一步商谈电话 小时服务信箱联系人 张海南顺祝商祺深圳市金海实业有限公司
# ```
# ```
# GG非常好的朋友H在计划马上的西藏自助游（完全靠搭车的那种），我和H也是很早认识的朋友，他有女朋友，在一起10年了，感情很好。
# GG对旅游兴趣不大。而且喜欢跟着旅行社的那种。所以肯定不去。
# 我在没有认识GG前，时常独自去一些地方，从南到北，觉得旅行不应该目的那么强。
# ```

# # 一、环境配置
#
# 本项目基于Paddle 2.0 编写，如果你的环境不是本版本，请先参考官网[安装](https://www.paddlepaddle.org.cn/install/quick) Paddle 2.0 。

# In[1]:


# 导入相关的模块

import re

import jieba

import os

import random

import paddle

import paddlenlp as ppnlp
from IPython import get_ipython

from paddlenlp.data import Stack, Pad, Tuple

import paddle.nn.functional as F

import paddle.nn as nn

import numpy as np

from functools import partial  # partial()函数可以用来固定某些参数值，并返回一个新的callable对象

# In[2]:


print(paddle.__version__)

# # 二、数据加载
#
# ## 2.1 数据集准备
#

# In[ ]:


# 解压数据集
# get_ipython().system('tar xvf data/data89631/trec06c.tgz')


# <font size=4>随机选择一个文件，查看邮件文件的具体内容。请注意，一定要设置`errors='ignore'`，否则可能因为个别字符无法识别，导致内容无法读取。</font>

# In[11]:


f = open('trec06c/data/100/005', 'r', encoding='gb2312', errors='ignore')

text = ''

for line in f:

    line = line.strip().strip('\n')

    if len(line) > 1:
        print(line)

        text = text + line

    # <font size=4>从文件内容看，读取后前半部分都是发件人、收件人、标题等格式信息，且可能由于编码问题，邮件标题无法解析，因此，我们还是采用提取邮件内容的方式。</font>


#
# <font size=4>考虑到不同邮件肯定长短不一，经过多次尝试，笔者认为只保留末尾200个字符，可以覆盖绝大多数邮件的内容。关于只保留邮件末尾内容对文本识别的影响，在项目[使用PaddleNLP进行恶意网页识别（一）](https://aistudio.baidu.com/aistudio/projectdetail/1812025)也进行过验证，效果良好。</font>

# ## 2.2 提取邮件内容，划分训练集、验证集、测试集

# In[7]:


# 去掉非中文字符

def clean_str(string):
    string = re.sub(r"[^\u4e00-\u9fff]", " ", string)

    string = re.sub(r"\s{2,}", " ", string)

    return string.strip()


# 从指定路径读取邮件文件内容信息

def get_data_in_a_file(original_path, save_path='all_email.txt'):
    email = ''

    f = open(original_path, 'r', encoding='gb2312', errors='ignore')

    # lines = f.readlines()

    for line in f:
        # 去掉换行符

        line = line.strip().strip('\n')

        # 去掉非中文字符

        line = clean_str(line)

        email += line

    f.close()

    # 只保留末尾200个字符

    return email[-200:]


# In[8]:


# 读取标签文件信息

f = open('trec06c/full/index', 'r')

for line in f:

    str_list = line.split(" ")

    # 设置垃圾邮件的标签为0

    if str_list[0] == 'spam':

        label = '0'

    # 设置正常邮件标签为1

    elif str_list[0] == 'ham':

        label = '1'

    text = get_data_in_a_file('trec06c/full/' + str(str_list[1].split("\n")[0]))

    with open("all_email.txt", "a+",encoding='gb2312') as f:

        f.write(text + '\t' + label + '\n')

# In[9]:


data_list_path = "./"

with open(os.path.join(data_list_path, 'eval_list.txt'), 'w', encoding='gb2312') as f_eval:
    f_eval.seek(0)

    f_eval.truncate()

with open(os.path.join(data_list_path, 'train_list.txt'), 'w', encoding='gb2312') as f_train:
    f_train.seek(0)

    f_train.truncate()

with open(os.path.join(data_list_path, 'test_list.txt'), 'w', encoding='gb2312') as f_test:
    f_test.seek(0)

    f_test.truncate()

with open(os.path.join(data_list_path, 'all_email.txt'), 'r', encoding='gb2312',errors='ignore') as f_data:
    lines = f_data.readlines()

i = 0

with open(os.path.join(data_list_path, 'eval_list.txt'), 'a', encoding='gb2312') as f_eval, open(
        os.path.join(data_list_path, 'test_list.txt'), 'a', encoding='gb2312') as f_test, open(
        os.path.join(data_list_path, 'train_list.txt'), 'a', encoding='gb2312') as f_train:
    for line in lines:

        words = line.split('\t')[-1].replace('\n', '')

        label = line.split('\t')[0]

        labs = ""

        # 划分验证集

        if i % 10 == 1:

            labs = label + '\t' + words + '\n'

            f_eval.write(labs)

        # 划分测试集

        elif i % 10 == 2:

            labs = label + '\t' + words + '\n'

            f_test.write(labs)

        # 划分训练集

        else:

            labs = label + '\t' + words + '\n'

            f_train.write(labs)

        i += 1

print("数据列表生成完成！")


# ## 2.3 自定义数据集

# In[2]:


class SelfDefinedDataset(paddle.io.Dataset):

    def __init__(self, data):
        super(SelfDefinedDataset, self).__init__()

        self.data = data

    def __getitem__(self, idx):
        return self.data[idx]

    def __len__(self):
        return len(self.data)

    def get_labels(self):
        return ["0", "1"]

#2
# class SelfDefinedDataset(paddle.io.Dataset):
#     def __init__(self, path):
#
#         def load_data_from_source(path):
#             data = []
#             for line in open(path,errors='ignore'):
#                 data.append(line.strip().split('\t'))
#             return data
#
#         self.data = load_data_from_source(path)
#
#     def __getitem__(self, idx):
#         return self.data[idx]
#
#     def __len__(self):
#         return len(self.data)




def txt_to_list(file_name):
    res_list = []

    for line in open(file_name ,'r',encoding='gb2312',errors="ignore"):
        res_list.append(line.strip().split('\t'))

    return res_list


trainlst = txt_to_list('train_list.txt')

devlst = txt_to_list('eval_list.txt')

testlst = txt_to_list('test_list.txt')

train_ds, dev_ds, test_ds = SelfDefinedDataset.get_datasets([trainlst, devlst, testlst])
# train_ds = SelfDefinedDataset('train_list.txt')
# dev_ds = SelfDefinedDataset('eval_list.txt')
# test_ds = SelfDefinedDataset('test_list.txt')
# In[3]:


# 获得标签列表

label_list = train_ds.get_labels()

# In[12]:


# 获得标签列表

#label_list = train_ds.get_labels()

# 看看数据长什么样子，分别打印训练集、验证集、测试集的前3条数据。

print("训练集数据：{}\n".format(train_ds[0:3]))

print("验证集数据:{}\n".format(dev_ds[0:3]))

print("测试集数据:{}\n".format(test_ds[0:3]))

print("训练集样本个数:{}".format(len(train_ds)))

print("验证集样本个数:{}".format(len(dev_ds)))

print("测试集样本个数:{}".format(len(test_ds)))

# ## 2.4 创建词表

# In[5]:


dict_path = 'webdict.txt'

# 创建数据字典，存放位置：webdict.txt。在生成之前先清空webdict.txt

# 在生成all_data.txt之前，首先将其清空

with open(dict_path, 'w') as f:
    f.seek(0)

    f.truncate()

dict_set = set()

train_data = open('all_email.txt',errors="ignore")

for data in train_data:

    seg = jieba.lcut(data[:-3])

    for datas in seg:

        if not datas is " ":
            dict_set.add(datas)

dicts = open(dict_path, 'w')

dicts.write('[PAD]\n')

dicts.write('[UNK]\n')

for data in dict_set:
    dicts.write(data + '\n')

dicts.close()


# In[6]:


# 加载词表

def load_vocab(vocab_file):
    """Loads a vocabulary file into a dictionary."""

    vocab = {}

    with open(vocab_file, "r", encoding="gb2312") as reader:
        tokens = reader.readlines()

    for index, token in enumerate(tokens):
        token = token.rstrip("\n").split("\t")[0]

        vocab[token] = index

    return vocab


vocab = load_vocab('./webdict.txt')

for k, v in vocab.items():
    print(k, v)

    break


# # 三、网络搭建
#
# ## 3.1 构造DataLoader
#
# 下面的`create_data_loader`函数用于创建运行和预测时所需要的`DataLoader`对象。
#
# - `paddle.io.DataLoader`返回一个迭代器，该迭代器根据`batch_sampler`指定的顺序迭代返回dataset数据。异步加载数据。
#
# - `batch_sampler`：DataLoader通过 batch\_sampler 产生的mini-batch索引列表来 dataset 中索引样本并组成mini-batch
#
# - `collate_fn`：指定如何将样本列表组合为mini-batch数据。传给它参数需要是一个callable对象，需要实现对组建的batch的处理逻辑，并返回每个batch的数据。在这里传入的是`prepare_input`函数，对产生的数据进行pad操作，并返回实际长度等。

# In[7]:


def convert_example(example, vocab, unk_token_id=1, is_test=False):
    """

    jieba 分词，转换id

    """

    input_ids = []

    for token in jieba.cut(example[0]):
        token_id = vocab.get(token, unk_token_id)

        input_ids.append(token_id)

    valid_length = np.array(len(input_ids), dtype='int64')

    if not is_test:

        label = np.array(example[-1], dtype="int64")

        return input_ids, valid_length, label

    else:

        return input_ids, valid_length


def convert_tokens_to_ids(tokens, vocab):
    """ Converts a token id (or a sequence of id) in a token string

        (or a sequence of tokens), using the vocabulary.

    """

    ids = []

    unk_id = vocab.get('[UNK]', None)

    for token in tokens:

        wid = vocab.get(token, unk_id)

        if wid:
            ids.append(wid)

    return ids


# In[8]:
#2222222
# from paddlenlp.datasets import load_dataset
#
# def read(data_path):
#     with open(data_path, 'r', encoding='gb2312',errors="ignore") as f:
#         # 跳过列名
#         next(f)
#         for line in f:
#             words, labels = line.strip('\n').split('\t')
#             words = words.split('\002')
#             labels = labels.split('\002')
#             yield {'text': words[0], 'label': labels[0]}
#
# # data_path为read()方法的参数
# train_ds = load_dataset(read,data_path='train_list.txt',splits='train',lazy=False)
# dev_ds = load_dataset(read,data_path='eval_list.txt',splits='dev',lazy=False)
# test_ds = load_dataset(read,data_path='test_list.txt',splits='test',lazy=False)

# Reads data and generates mini-batches.

def create_dataloader(dataset,
                      trans_function=None,
                      mode='train',
                      batch_size=1,
                      pad_token_id=0,
                      batchify_fn=None):
    if trans_function:
        dataset = dataset.apply(trans_function, lazy=True)

    # return_list 数据是否以list形式返回

    # collate_fn  指定如何将样本列表组合为mini-batch数据。传给它参数需要是一个callable对象，需要实现对组建的batch的处理逻辑，并返回每个batch的数据。在这里传入的是`prepare_input`函数，对产生的数据进行pad操作，并返回实际长度等。

    dataloader = paddle.io.DataLoader(
        dataset,
        return_list=True,
        batch_size=batch_size,
        collate_fn=batchify_fn)

    return dataloader


# python中的偏函数partial，把一个函数的某些参数固定住（也就是设置默认值），返回一个新的函数，调用这个新函数会更简单。

trans_function = partial(

    convert_example,

    vocab=vocab,

    unk_token_id=vocab.get('[UNK]', 1),

    is_test=False)

# 将读入的数据batch化处理，便于模型batch化运算。

# batch中的每个句子将会padding到这个batch中的文本最大长度batch_max_seq_len。

# 当文本长度大于batch_max_seq时，将会截断到batch_max_seq_len；当文本长度小于batch_max_seq时，将会padding补齐到batch_max_seq_len.

batchify_fn = lambda samples, fn=Tuple(

    Pad(axis=0, pad_val=vocab['[PAD]']),  # input_ids

    Stack(dtype="int64"),  # seq len

    Stack(dtype="int64")  # label

): [data for data in fn(samples)]

train_loader = create_dataloader(

    train_ds,

    trans_function=trans_function,

    batch_size=32,

    mode='train',

    batchify_fn=batchify_fn)

dev_loader = create_dataloader(

    dev_ds,

    trans_function=trans_function,

    batch_size=32,

    mode='validation',

    batchify_fn=batchify_fn)

test_loader = create_dataloader(

    test_ds,

    trans_function=trans_function,

    batch_size=32,

    mode='test',

    batchify_fn=batchify_fn)


# ## 3.2 Encoder部分
#
# 使用`LSTMencoder`搭建一个BiLSTM模型用于进行句子建模，得到句子的向量表示。
#
# ![](https://ai-studio-static-online.cdn.bcebos.com/105dea97774c4cdfb3bd9a6d36277192407ba3accc674f53aa2a26e0398747e5)
#
#
# 然后接一个线性变换层，完成二分类任务。
#
# - `paddle.nn.Embedding`组建word-embedding层
# - `ppnlp.seq2vec.LSTMEncoder`组建句子建模层
# - `paddle.nn.Linear`构造二分类器
#
# 参考资料：[Recurrent Neural Network Regularization](https://arxiv.org/pdf/1409.2329.pdf)

# In[9]:


class LSTMModel(nn.Layer):

    def __init__(self,

                 vocab_size,

                 num_classes,

                 emb_dim=64,

                 padding_idx=0,

                 lstm_hidden_size=96,

                 direction='forward',

                 lstm_layers=2,

                 dropout_rate=0,

                 pooling_type=None,

                 fc_hidden_size=48):
        super().__init__()

        # 首先将输入word id 查表后映射成 word embedding

        self.embedder = nn.Embedding(

            num_embeddings=vocab_size,

            embedding_dim=emb_dim,

            padding_idx=padding_idx)

        # 将word embedding经过LSTMEncoder变换到文本语义表征空间中

        self.lstm_encoder = ppnlp.seq2vec.LSTMEncoder(

            emb_dim,

            lstm_hidden_size,

            num_layers=lstm_layers,

            direction=direction,

            dropout=dropout_rate,

            pooling_type=pooling_type)

        # LSTMEncoder.get_output_dim()方法可以获取经过encoder之后的文本表示hidden_size

        self.fc = nn.Linear(self.lstm_encoder.get_output_dim(), fc_hidden_size)

        # 最后的分类器

        self.output_layer = nn.Linear(fc_hidden_size, num_classes)

    def forward(self, text, seq_len):
        # text shape: (batch_size, num_tokens)

        # print('input :', text.shape)

        # Shape: (batch_size, num_tokens, embedding_dim)

        embedded_text = self.embedder(text)

        # print('after word-embeding:', embedded_text.shape)

        # Shape: (batch_size, num_tokens, num_directions*lstm_hidden_size)

        # num_directions = 2 if direction is 'bidirectional' else 1

        text_repr = self.lstm_encoder(embedded_text, sequence_length=seq_len)

        # print('after lstm:', text_repr.shape)

        # Shape: (batch_size, fc_hidden_size)

        fc_out = paddle.tanh(self.fc(text_repr))

        # print('after Linear classifier:', fc_out.shape)

        # Shape: (batch_size, num_classes)

        logits = self.output_layer(fc_out)

        # print('output:', logits.shape)

        # probs 分类概率值

        probs = F.softmax(logits, axis=-1)

        # print('output probability:', probs.shape)

        return probs


model = LSTMModel(
    len(vocab),
    #len(label_list),
    direction='bidirectional',
    padding_idx=vocab['[PAD]'],
    num_classes=len(label_list)
)

model = paddle.Model(model)

# # 四、训练模型
#
# 接下来开始训练模型。
#
# - 在训练过程中，使用VisualDL记录训练过程并进行可视化

# In[10]:


optimizer = paddle.optimizer.Adam(

    parameters=model.parameters(), learning_rate=1e-4)

loss = paddle.nn.CrossEntropyLoss()

metric = paddle.metric.Accuracy()

model.prepare(optimizer, loss, metric)

# In[11]:


# 设置visualdl路径

log_dir = './visualdl'

callback = paddle.callbacks.VisualDL(log_dir=log_dir)

# In[13]:


model.fit(train_loader, dev_loader, epochs=2, save_dir='./checkpoints', save_freq=5, callbacks=callback)

# ![](https://ai-studio-static-online.cdn.bcebos.com/3d7b45ec9c5e45da9c315635a217f39df7d901f7ecb941a2907fce25c372ee03)

# <font size=4>只需5~10个epochs，模型在验证集上的准确性就达到98.5%，效果果然是非常好的。</font>
#
# <font size=4>参考trec06c中文邮件分类传统机器学习方法的实现，[朴素贝叶斯算法详解](https://aistudio.baidu.com/aistudio/projectdetail/1215368) by [@7hinc](https://aistudio.baidu.com/aistudio/personalcenter/thirdview/212554)，随机森林模型准确率在85%左右，看来，深度学习方法和LSTM模型在长文本分类上，优势是非常明显的。</font>

# In[13]:


results = model.evaluate(dev_loader)

print("Finally test acc: %.5f" % results['acc'])

# # 五、预测效果
#
# 完成上面的模型训练之后，可以得到一个能够通过网页内容识别恶意网页的模型。接下来查看模型在测试集上的泛化能力。
#
# 模型的效果良好，再次得到印证！![https://ai-studio-static-online.cdn.bcebos.com/d500f483e55544a5b929abad59de208f180c068cc81648009fab60a0b6d9bda2](https://ai-studio-static-online.cdn.bcebos.com/d500f483e55544a5b929abad59de208f180c068cc81648009fab60a0b6d9bda2)

# In[15]:


label_map = {0: '垃圾邮件', 1: '正常邮件'}

results = model.predict(test_loader)[0]

predictions = []

for batch_probs in results:
    # 映射分类label

    idx = np.argmax(batch_probs, axis=-1)

    idx = idx.tolist()

    labels = [label_map[i] for i in idx]

    predictions.extend(labels)

# 看看预测数据前5个样例分类结果

for idx, data in enumerate(test_ds.data[:5]):
    print('Data: {} \t Label: {}'.format(data[0], predictions[idx]))

# # 参考资料
# - PaddleNLP文本分类入门系列
# 	- [PaddleNLP玩转谣言检测](https://aistudio.baidu.com/aistudio/projectdetail/1623174?channelType=0&channel=0) by [@七年期限](https://aistudio.baidu.com/aistudio/personalcenter/thirdview/58637)
# 	- [【AI创造营】网抑云选手等级鉴别器](https://aistudio.baidu.com/aistudio/projectdetail/1687287?channelType=0&channel=0) by [@红龙的梦](https://aistudio.baidu.com/aistudio/personalcenter/thirdview/680948)
# - trec06c数据集高效处理和传统机器学习分类方法实现
# 	- [朴素贝叶斯算法详解](https://aistudio.baidu.com/aistudio/projectdetail/1215368) by [@7hinc](https://aistudio.baidu.com/aistudio/personalcenter/thirdview/212554)
# - 早期PaddlePaddle版本的垃圾邮件分类系统
# 	- [PaddlePaddle垃圾邮件处理实战（一）](https://www.cnblogs.com/charlotte77/archive/2018/05/22/9071775.html)
# 	- [PaddlePaddle垃圾邮件处理实战（二）](https://blog.csdn.net/weixin_33895695/article/details/85995173)

# # 小结
#
# 1\. 遇到问题时，多找些参考资料，有些平台上就有，善用搜索有惊喜！
#
# 2\. 初探中文邮件分类，在未使用finetune的情况下，效果已经非常不错了，当然，这可能是数据集问题，后续将结合其它来源的垃圾邮件数据集，进行尝试。
#
# 3\. 分离解析邮件头信息和邮件内容是否有更好的方法，后续将进一步探索。
